#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>

#define MAX_PATH 260
#define MAX_FILES 1024
#define BUFFER_SIZE 4096

typedef struct {
    char path[MAX_PATH];
    long size;
    int is_dir;
} FileEntry;

FileEntry files[MAX_FILES];
int file_count = 0;
clock_t start_time;


void rle_compress(FILE *input, FILE *output) {
    unsigned char buffer[BUFFER_SIZE];
    unsigned char last;
    int count = 1;
    int non_repeat_count = 0;
    size_t bytes_read;
    unsigned char non_repeat_buffer[127]; 

    while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, input))) {
        for (size_t i = 0; i < bytes_read; i++) {
            if (i == 0) {
                last = buffer[i];
                non_repeat_buffer[0] = buffer[i];
                non_repeat_count = 1;
                continue;
            }

            if (buffer[i] == last) {
                if (non_repeat_count > 1) {
                    unsigned char count_byte = non_repeat_count - 1; 
                    fwrite(&count_byte, 1, 1, output);
                    fwrite(non_repeat_buffer, 1, non_repeat_count, output);
                    non_repeat_count = 0;
                }
                count++;
                if (count == 127) {
                    unsigned char count_byte = 0x80 | (count - 1);
                    fwrite(&count_byte, 1, 1, output);
                    fwrite(&last, 1, 1, output);
                    count = 0;
                }
                last = buffer[i];
            } else {
                if (count > 1) {
        
                    unsigned char count_byte = 0x80 | (count - 1); 
                    fwrite(&count_byte, 1, 1, output);
                    fwrite(&last, 1, 1, output);
                    count = 1;
                }
                if (non_repeat_count < 127) {
                    non_repeat_buffer[non_repeat_count++] = buffer[i];
                } else {
                    unsigned char count_byte = non_repeat_count - 1; 
                    fwrite(&count_byte, 1, 1, output);
                    fwrite(non_repeat_buffer, 1, non_repeat_count, output);
                    non_repeat_count = 0;
                    non_repeat_buffer[non_repeat_count++] = buffer[i];
                }
                last = buffer[i];
            }
        }
    }

    if (count > 1) {
        unsigned char count_byte = 0x80 | (count - 1);
        fwrite(&count_byte, 1, 1, output);
        fwrite(&last, 1, 1, output);
    } else if (non_repeat_count > 0) {
        unsigned char count_byte = non_repeat_count - 1;
        fwrite(&count_byte, 1, 1, output);
        fwrite(non_repeat_buffer, 1, non_repeat_count, output);
    }
}


void collect_files(const char *path, const char *base_path) {
    DIR *dir;
    struct dirent *entry;
    struct stat statbuf;
    char full_path[MAX_PATH];

    if ((dir = opendir(path)) == NULL) {
        if (stat(path, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) {
            strcpy(files[file_count].path, path);
            files[file_count].size = statbuf.st_size;
            files[file_count].is_dir = 0;
            file_count++;
        }
        return;
    }

    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
            continue;

        snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
        
        if (stat(full_path, &statbuf) != 0)
            continue;

        if (S_ISDIR(statbuf.st_mode)) {
            strcpy(files[file_count].path, full_path);
            files[file_count].size = 0;
            files[file_count].is_dir = 1;
            file_count++;
            collect_files(full_path, base_path);
        } else {
            strcpy(files[file_count].path, full_path);
            files[file_count].size = statbuf.st_size;
            files[file_count].is_dir = 0;
            file_count++;
        }
    }
    closedir(dir);
}

int main(int argc, char *argv[]) {
    if (argc < 3) {
        printf("Usage: rlezip <input1> [input2...] <output>\n");
        return 1;
    }

    start_time = clock();
    const char *output_path = argv[argc - 1];
    FILE *output = fopen(output_path, "wb");
    if (!output) {
        perror("Failed to open output file");
        return 1;
    }

    fwrite("RLEZ", 1, 4, output);
    int num_files = argc - 2;
    fwrite(&num_files, sizeof(int), 1, output);

    long total_original_size = 0;
    long total_compressed_size = 0;

    for (int i = 1; i < argc - 1; i++) {
        if (strcmp(argv[i], "*") == 0) {
            collect_files(".", ".");
        } 
        else if (opendir(argv[i]) != NULL) {
            collect_files(argv[i], argv[i]);
        }
        else {
            struct stat statbuf;
            if (stat(argv[i], &statbuf) == 0 && S_ISREG(statbuf.st_mode)) {
                strcpy(files[file_count].path, argv[i]);
                files[file_count].size = statbuf.st_size;
                files[file_count].is_dir = 0;
                file_count++;
            }
        }
    }

    for (int i = 0; i < file_count; i++) {
        int path_len = strlen(files[i].path);
        fwrite(&path_len, sizeof(int), 1, output);
        fwrite(files[i].path, 1, path_len, output);
        
        fwrite(&files[i].size, sizeof(long), 1, output);
        fwrite(&files[i].is_dir, sizeof(int), 1, output);

        if (!files[i].is_dir) {
            FILE *input = fopen(files[i].path, "rb");
            if (!input) {
                perror("Failed to open input file");
                continue;
            }

            long start_pos = ftell(output);
            rle_compress(input, output);
            long end_pos = ftell(output);
            fclose(input);

            total_original_size += files[i].size;
            total_compressed_size += (end_pos - start_pos);
        }
    }

    fclose(output);
    clock_t end_time = clock();

    double time_used = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
    double ratio = (total_original_size > 0) ? 
        (double)total_compressed_size / total_original_size * 100 : 0;

    printf("Compression completed:\n");
    printf("  Original size: %ld bytes\n", total_original_size);
    printf("  Compressed size: %ld bytes\n", total_compressed_size);
    printf("  Compression ratio: %.2f%%\n", ratio);
    printf("  Time used: %.2f seconds\n", time_used);

    return 0;
}
